/*  lex.cpp

Lex class for scanning and parsing Lexemes
from the source file.

Nicholas Green
Lexical Analyzer
*/

#include "lex.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>

#ifdef _WIN32
//Apparently there isn't any strnup in Windows. o_0
char *strndup(const char *string,size_t s)
{
  char *p,*r;
  if (string == NULL)
    return NULL;
  p = (char*)string;
  while (s == 0)
  {
    if (*p == 0)
      break;
    p++;
    s--;
  }
  s = (p - string);
  r = (char*)malloc(1+s);
  if (r)
  {
    strncpy(r,string,s);
    r[s] = 0;
  }
  return r;
}
#endif

Lex::Lex(FILE* fp)
{
  file = fp;
  stack = '\0';
  stack_available = true;
  lines = 1;
}

Lex::~Lex(void){}

Lexeme* Lex::lex()
{
  char ch;
  skipWhiteSpace();
  ch = getNextChar();

  //Non-type/var symbols
  switch (ch)
  {
  case EOF:
    return new Lexeme(END_OF_FILE,lines);
    break;
  case ',':
    return new Lexeme(COMMA,lines);
    break;
  case '(':
    return new Lexeme(OPAREN,lines);
    break;
  case ')':
    return new Lexeme(CPAREN,lines);
    break;
  case '{':
    return new Lexeme(OBRACE,lines);
    break;
  case '}':
    return new Lexeme(CBRACE,lines);
    break;
  case '[':
    return new Lexeme(OBRACK,lines);
    break;
  case ']':
    return new Lexeme(CBRACK,lines);
    break;
  case '\\':
    return new Lexeme(BSLASH,lines);
    break;
  case '.':
    return new Lexeme(DOT,lines);
    break;
  case ':':
    return new Lexeme(COLON,lines);
    break;
  case ';':
    return new Lexeme(SEMI,lines);
    break;
  default:
    {
      //Comments
      if (ch == '/')
      {
        char next = getNextChar();
        if(next == '/' || next == '*')
        {
          //return lexComment(next);
          lexComment(next); //dont' return anything
        }
        else
        {
          return new Lexeme(BAD_CHAR,lines);
        }
      }
      //Integer
      if (isdigit(ch))
      {
        return lexInteger(ch);
      }
      //Strings
      else if (validTypeOrVar(ch))
      {
        return lexString(ch);
      }
      //Operators
      else if (!lexOpDelimiter(ch))
      {
        return lexOp(ch);
      }
      //Invalid - Bad character
      else
      {
        return new Lexeme(BAD_CHAR,lines);
      }
    }
  }
}

//Valid Type or Character
bool Lex::validTypeOrVar(char ch)
{
  bool valid = true;
  if(ch == '&')
  {
    char next = getNextChar();
    if(next == ' ')
    {            
      valid = false;
    }
    ungetc(next,file);
  }
  if(valid)
    valid = isalpha(ch) || ch == '_' || ch == '\"' || ch == '\'' || ch == '&';
  return valid;
}

//Comments
Lexeme* Lex::lexComment(char ch)
{
  char comment_buffer[MAX_SIZE];
  if(ch == '/') //single line comment
  {
    int i = 0;
    while(ch != '\r' && ch != '\n' && i < MAX_SIZE)
    {
      ch = getNextChar();
      comment_buffer[i++] = ch;
    }
    comment_buffer[i-1] = '\0';
    ungetc(ch,file);
    return new Lexeme(COMMENT,comment_buffer,lines);
  }
  else if(ch == '*') //block comment
  {
    bool done = false;
    int i = 0;
    ch = getNextChar();
    while(!done && i < MAX_SIZE)
    {
      if(ch == '*')
      {
        ch = getNextChar();
        if(ch == '/')
        {
          done = true;
        }
      }
      if(ch == EOF)
      {
        ungetc(ch, file);
        return new Lexeme(COMMENT,comment_buffer,lines);
      }
      comment_buffer[i++] = ch;
      ch = getNextChar();
    }
    comment_buffer[i-1] = '\0';
    ungetc(ch,file);
    return new Lexeme(COMMENT,comment_buffer,lines);
  }
  else
    return new Lexeme(BAD_CHAR,lines);
}

//Skip the whitespaces in the file
bool Lex::skipWhiteSpace()
{
  char c = '\0';
  while ((c == ' ') || (c == '\0') || (c == '\t') || (c == '\n') || (c == '\r'))
  {
    c = getNextChar();
    if (c == EOF)
      return false;
  }
  ungetc(c, file);
  return true;
}

//Get the next character in the file
char Lex::getNextChar()
{
  char c = getc(file);
  if(c == '\n')
    lines++;
  return c;
}

//Push a character on the stack
void Lex::pushStack(char ch)
{
  assert(stack_available);
  stack = ch;
  stack_available = false;
}

//Pop a character off the stack
char Lex::popStack()
{
  assert(!stack_available);
  stack_available = true;
  char ch = stack;
  stack = '\0';
  return ch;
}

//Integer Lexeme
Lexeme* Lex::lexInteger(char ch)
{
  int index = 1;
  char buffer[512];
  memset(buffer,0,sizeof(buffer));
  buffer[0] = ch;
  buffer[1] = '\0';

  ch = getNextChar();
  while (lexSymDelimiter(ch) && (isdigit(ch) || ch == '.'))
  {

    buffer[index++] = ch;
    ch = getNextChar();
  }
  ungetc(ch, file);
  buffer[index] = '\0';
  int x = atoi(buffer);

  return new Lexeme(INTEGER, x,lines);
}

//Integer Real - Not implemented
Lexeme* Lex::lexReal(char ch)
{
  return new Lexeme(REAL,lines);
}

//Symbol Delimiter for Strings
bool Lex::lexSymDelimiter(char ch)
{
  switch (ch)
  {
  case EOF:
  case ' ':
  case '(':
  case ')':
  case ',':
  case ':':
  case ';':
  case '{':
  case '}':
  case '\r':
  case '\n':
    return false;
    break;
  default:
    return true;
  }
}

Lexeme* Lex::fStyle(char* buffer, int size)
{
  //If a string, cat until quotation mark
  if(buffer[0] == '"')
  {
    char ch = buffer[size-1];
    while(ch != '"' && (size < 512 - 1))
    {
      ch = getNextChar();
      buffer[(++size) - 1] = ch;
    }
    buffer[size] = '\0';
  }

  //Normal Type   
  return isType(buffer, size);

}


//String Lexeme - Types and Variables
Lexeme* Lex::lexString(char ch)
{
  int index = 1;
  char buffer[512] = {};
  buffer[0] = ch;
  buffer[1] = '\0';

  ch = getNextChar();
  while (lexSymDelimiter(ch))
  {

    buffer[index++] = ch;
    ch = getNextChar();
  }
  ungetc(ch, file);
  buffer[index] = '\0';

  return fStyle(buffer,index);

}

Lexeme* Lex::isType(const char* buffer, int size)
{
  if (strcmp(buffer, "if") == 0)
    return new Lexeme(COND_IF,lines);
  else if (strcmp(buffer, "else") == 0)
    return new Lexeme(COND_ELSE,lines);
  else if (strcmp(buffer, "define") == 0)
    return new Lexeme(DEFINE,lines);
  else if (strcmp(buffer, "switch") == 0)
    return new Lexeme(COND_SWITCH,lines);
  else if(strcmp(buffer, "case") == 0)
    return new Lexeme(COND_CASE,lines);
  else if(strcmp(buffer, "default") == 0)
    return new Lexeme(COND_DEFAULT,lines);
  else if(strcmp(buffer, "break") == 0)
    return new Lexeme(BREAK,lines);
  else if(strcmp(buffer,"while") == 0)
    return new Lexeme(COND_WHILE,lines);
  else if(strcmp(buffer,"display") == 0)
    return new Lexeme(DISPLAY, lines);
  else if(strcmp(buffer,"displayn") == 0)
    return new Lexeme(DISPLAYN, lines);
  else if (buffer[0] == '"' && buffer[size - 1] == '"')
    return new Lexeme(STRING, buffer,lines);
  else if (buffer[0] == '\'' && buffer[size - 1] == '\'')
    return new Lexeme(CHAR, buffer,lines);
  else if (strcmp(buffer, "var") == 0)
    return new Lexeme(VARDEF,lines);
  else
  {
    return new Lexeme(ID, buffer, lines);
  }
}

//Operator Delimiters
bool Lex::lexOpDelimiter(char ch)
{
  switch (ch)
  {
  case '=':
  case '^':
  case '&':
  case '|':
  case '!':
  case '<':
  case '>':
  case '+':
  case '-':
  case '*':
  case '/':
  case '%':
  case '~':
    return false;
    break;
  default:
    return true;
  }
}

//Operator Lexeme
Lexeme* Lex::lexOp(char ch)
{
  char buffer[3];
  buffer[0] = ch;
  buffer[1] = getNextChar();
  buffer[2] = '\0';
  if (!lexOpDelimiter(buffer[1])) //double chars
  {
    if (strcmp(buffer, "==") == 0)
      return new Lexeme(COP_EQUAL,lines);
    else if (strcmp(buffer, "!=") == 0)
      return new Lexeme(COP_NEQUAL,lines);
    else if (strcmp(buffer, ">=") == 0)
      return new Lexeme(COP_GRTEQU,lines);
    else if (strcmp(buffer, "<=") == 0)
      return new Lexeme(COP_LESEQU,lines);
    else if (strcmp(buffer, "&&") == 0)
      return new Lexeme(LOP_AND,lines);
    else if (strcmp(buffer, "||") == 0)
      return new Lexeme(LOP_OR,lines);
    else if (strcmp(buffer, "<<") == 0)
      return new Lexeme(BOP_LSFT,lines);
    else if (strcmp(buffer, ">>") == 0)
      return new Lexeme(BOP_RSFT,lines);
    else
      return new Lexeme(BAD_CHAR,lines);
  }
  else //single chars
  {
    ungetc(buffer[1], file);
    buffer[1] = '\0';
    switch (buffer[0])
    {
    case '=':
      return new Lexeme(OP_ASN,lines);
      break;
    case '+':
      return new Lexeme(AOP_ADD,lines);
      break;
    case '-':
      return new Lexeme(AOP_SUB,lines);
      break;
    case '*':
      return new Lexeme(AOP_MUL,lines);
      break;
    case '/':
      return new Lexeme(AOP_DIV,lines);
      break;
    case '%':
      return new Lexeme(AOP_MOD,lines);
      break;
    case '>':
      return new Lexeme(COP_GREATER,lines);
      break;
    case '<':
      return new Lexeme(COP_LESS,lines);
      break;
    case '!':
      return new Lexeme(LOP_NOT,lines);
      break;
    case '~':
      return new Lexeme(BOP_NOT,lines);
      break;
    case '&':
      return new Lexeme(BOP_AND,lines);
      break;
    case '|':
      return new Lexeme(BOP_OR,lines);
      break;
    case '^':
      return new Lexeme(BOP_XOR,lines);
      break;
    default:
      return new Lexeme(BAD_CHAR,lines);

    }
  }
}
